package org.test4j.mock.faking.fluent;

import org.test4j.mock.Invocation;
import org.test4j.mock.MockUp;
import org.test4j.mock.faking.meta.FakeMethod;
import org.test4j.mock.faking.meta.FakeMethods;
import org.test4j.mock.faking.meta.FakeStates;
import org.test4j.mock.faking.meta.MethodId;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import static org.test4j.mock.faking.fluent.MockMethod.INDEX_REST_BEHAVIOR;
import static org.test4j.mock.faking.util.ObjectIdentify.identities;
import static org.test4j.mock.faking.util.ReflectUtility.doThrow;

/**
 * 用来构造fluent mock up的基类
 *
 * @param <T>
 * @author darui.wu
 */
public class FluentMockUp<T> extends MockUp<T> {
    /**
     * key: methodName+parasDescription
     * value: (key:index, value:behavior)
     */
    protected final MethodBehaviors behaviors;


    public FluentMockUp() {
        this.behaviors = new MethodBehaviors(super.declaredToFake);
    }

    public FluentMockUp(Class declaredToFake) {
        super(declaredToFake);
        this.behaviors = new MethodBehaviors(super.declaredToFake);
    }

    public FluentMockUp(String fullClassName, Object[] objects) {
        super(fullClassName, identities(objects));
        this.behaviors = new MethodBehaviors(super.declaredToFake);
    }

    public FluentMockUp(Class declaredClass, MethodBehaviors behaviors, Set<Integer> fakedHashCodes) {
        super(declaredClass, fakedHashCodes);
        this.behaviors = behaviors;
    }

    protected final void initFakeMethods() {
        // 去掉基类在构造函数中的实现
    }

    public final void apply() {
        this.init();
        this.initFluentMethods();
    }

    private void initFluentMethods() {
        Set<MethodId> metas = this.behaviors.getMethodIds();
        FakeMethods fakeMethods = new FakeMethods(fakedSeqNo);
        for (MethodId meta : metas) {
            fakeMethods.add(new FakeMethod(this, meta));
        }
        FakeStates.register(fakeMethods);
    }

    protected void init() {
    }

    /**
     * mock方法第index次调用
     */
    public void mockMethod(String realClass, String name, String desc, int index, FakeFunction fake) {
        this.behaviors.addMockBehavior(realClass, name, desc, index, fake);
    }

    public void mockReturn(String realClass, String name, String desc, int index, Object result) {
        this.behaviors.addMockBehavior(realClass, name, desc, index, FakeResult.value(result));
    }

    public void mockThrows(String realClass, String name, String desc, int index, Throwable e) {
        this.behaviors.addMockBehavior(realClass, name, desc, index, FakeResult.eject(e));
    }

    /**
     * 根据预定义的行为, 执行mock方法
     *
     * @param invocation mock调用代理
     * @param methodDesc 调用方法描述
     * @param args       方法实际入参
     * @return mock执行结果
     * @throws Throwable
     */
    public Object invoke(Invocation invocation, String methodDesc, Object[] args) {
        int index = invocation.getInvokedTimes();
        this.assertBefore(methodDesc, invocation);
        MockBehavior behavior = this.getBehavior(index, methodDesc);
        if (behavior == null) {
            Object result = invocation.proceed(args);
            return this.assertAfter(methodDesc, invocation, result);
        } else if (behavior instanceof FakeFunction) {
            return ((FakeFunction) behavior).doFake(invocation);
        }
        FakeResult result = (FakeResult) behavior;
        if (result.isThrowable()) {
            return doThrow((Throwable) result.getResult());
        } else {
            return this.assertAfter(methodDesc, invocation, result.getResult());
        }
    }

    private Map<String, Consumer<Invocation>> parasAsserts = new HashMap<>();

    private Map<String, BiConsumer<Invocation, Object>> resultAsserts = new HashMap<>();

    void addAssertMethodParas(String methodDesc, Consumer<Invocation> asserts) {
        this.parasAsserts.put(methodDesc, asserts);
    }

    void addAssertMethodResult(String methodDesc, BiConsumer<Invocation, Object> asserts) {
        this.resultAsserts.put(methodDesc, asserts);
    }

    /**
     * 断言方法入参
     *
     * @param methodDesc
     * @param invocation
     */
    private void assertBefore(String methodDesc, Invocation invocation) {
        Consumer<Invocation> asserts = parasAsserts.get(methodDesc);
        if (asserts != null) {
            asserts.accept(invocation);
        }
    }

    /**
     * 断言方法结果值
     *
     * @param methodDesc
     * @param invocation
     * @param result
     */
    private Object assertAfter(String methodDesc, Invocation invocation, Object result) {
        BiConsumer<Invocation, Object> asserts = this.resultAsserts.get(methodDesc);
        if (asserts != null) {
            asserts.accept(invocation, result);
        }
        return result;
    }

    /**
     * 根据方法执行序号返回预定义行为
     *
     * @param index      方法实际执行序号(从1开始)
     * @param methodDesc 方法描述
     * @return
     */
    private MockBehavior getBehavior(int index, String methodDesc) {
        Map<Integer, MockBehavior> methods = this.behaviors.get(methodDesc);
        if (methods == null || methods.isEmpty()) {
            return null;
        }
        MockBehavior behavior = methods.get(index);
        if (behavior == null) {
            behavior = methods.get(INDEX_REST_BEHAVIOR);
        }
        return behavior;
    }
}